/**
 *  Copyright 2013 Nate Dierk
 *
 *  COMP 590 -- Fall 2013
 */

#define MAX_MATRICES 64

#include "GPoint.h"
#include "GMatrix_nd.h"
#include "GBlitter_nd.cpp"
#include "GEdge_nd.cpp"

class GContext0 : public GContext {
protected:
  GBitmap fBmp;
  GMatrix fMatStack[MAX_MATRICES];
  int fStackTop;

  GMatrix* getMatrix(){
  	return &fMatStack[fStackTop];
  };

  // method for blitting triangulated area between GPoints
  void blitPolygon(GBlitter* blit, const GPoint* verts, int count){
    GRect r;        //if area is 0, quit
    if (r.setBounds(verts, count).isEmpty()){ return; }

    // get array of sorted edges -- sorted by asc. y, then by asc. x
    GAutoSArray<GEdge, 32> edges = GAutoSArray<GEdge, 32>(3 * count);
    int edgeCount = makeXformedEdges(&fBmp, getMatrix(), verts, count, edges.get());

    int i = 1;
    GEdge *left = &edges[0], *right = &edges[1];
    while (i < edgeCount){
        if (left->fRowsLeft <= 0){ left = &edges[++i]; continue; } // replace spent edges
        if (right->fRowsLeft <= 0){ right = &edges[++i]; continue; } 

        // compute bounds and blit row
        int x = left->getStart(), y = left->fCurY;
        int count = std::min( (int) ceilf(right->fCurX) - x, //difference in edges
                              fBmp.fWidth - x); // max possible in row
        blit->blitRow(x, y, count);

        left->step(); // step edges by slope, decrease remaining rows
        right->step();
      }
  }

public:
    GContext0(const GBitmap& bmp) {
      fBmp = bmp;
      fStackTop = 0;
      fMatStack[0] = GMatrix(); //set matrix to identity
  }
    virtual ~GContext0() {}


    // *  Return the information about the context's bitmap
    void getBitmap(GBitmap* bmp) const {
      *bmp = fBmp;
    }


   
    ///////////////////////////////////////////////////////
    // Drawing Functions

    void clear(const GColor& color){
      ClearBlitter cb = ClearBlitter(&fBmp, color);
     
      if (fBmp.fRowBytes == fBmp.fWidth * sizeof(GPixel)){  // if no slop
        cb.blitRow(0, 0, fBmp.fWidth * fBmp.fHeight); // blit contiguously
      } else {                                 //otherwise blit row by row
        for (int row = 0; row < fBmp.fHeight; ++row){
          cb.blitRow(0, row, fBmp.fWidth);
        }
      }
    }

    void drawRect(const GRect& rect, const GPaint& paint){
      if (0.0f == paint.getAlpha()) { return; } // if 0 alpha quit

      RectBlitter blit = RectBlitter(&fBmp, paint);

      if (getMatrix()->isRotated()){
        GAutoArray<GPoint> quad(4); //make as GPoints
        rect.toQuad(quad);
        blitPolygon(&blit, quad, 4);
      } else {
          GIRect xformed = getMatrix()->xformRect(rect).round(); // transform rectangle
          GIRect clip;

          if (clip.setIntersection(xformed, fBmp.asIRect())){ // clip to intersection with bmp
            for (int row = clip.fTop; row < clip.fBottom; ++row){
              blit.blitRow(clip.x(), row, clip.width());
            }
          }
        }
    }

    void drawBitmap(const GBitmap& bmp, float x, float y, const GPaint& paint){
      if (0.0f == paint.getAlpha()) { return; } // if 0 alpha quit

      onSave();
      translate(x, y);

      if (getMatrix()->isRotated()) {

      	GMatrix inv = getMatrix()->inverse();
      	BmpBlitter blit = BmpBlitter(&fBmp, bmp, &inv, paint.getAlpha());

      	GAutoArray<GPoint> quad(4); //make as GPoints
        GRect::MakeWH(bmp.fWidth, bmp.fHeight).toQuad(quad);
        blitPolygon(&blit, quad, 4);

      } else {

      	GIRect xformed = getMatrix()->xformRect(bmp.asIRect()).round();
      	GIRect clip;
      	GMatrix inv = getMatrix()->inverse();

      	if (clip.setIntersection(xformed, fBmp.asIRect())) {
        //create blitter and blit all pixels
      		BmpBlitter blit = BmpBlitter(&fBmp, bmp, &inv, paint.getAlpha());
      		int count = clip.width(), start = clip.fLeft;
      		for (int row = clip.fTop; row < clip.fBottom; ++row){
      			blit.blitRow(start, row, count);
      		}
      	}
      }

      onRestore();
    }

    void drawTriangle(const GPoint vertices[3], const GPaint& paint){
    	drawConvexPolygon(vertices, 3, paint);
    }

    void drawConvexPolygon(const GPoint vertices[], int count, const GPaint& paint){
      if (0.0f == paint.getAlpha()) { return; } // if 0 alpha quit

      RectBlitter blit = RectBlitter(&fBmp, paint);
      blitPolygon(&blit, vertices, count);
    }



    ///////////////////////////////////////////////////////
    // CTM modification functions

    void translate(float x, float y){
      getMatrix()->precat(GMatrix::translate(x,y));
    }

    void scale(float x, float y){
      getMatrix()->precat(GMatrix::scale(x,y));
    }

    void rotate(float rad){
      getMatrix()->precat(GMatrix::rotate(rad));
    }

    void onSave(){
      if (fStackTop+1 < MAX_MATRICES){
        fMatStack[fStackTop+1] = fMatStack[fStackTop]; //duplicate matrix
        ++fStackTop;
      }
    }

    void onRestore(){
      if (fStackTop > 0){
      --fStackTop; // pop off most recent changes
    }
  }
};